package co.fitstart.entity.utils;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

public class DateUtils {
    
    private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";

    /**
     * Transforms the date to the time of beginning of that date.
     * <p> 
     * For instance: 2015-11-25 17:49:00 will be transformed to 2015-11-25 00:00:00
     * 
     * @param   date the date needs to be transformed
     * @return  date represents the beginning time of given date
     * @see     #parseDate(String, String)
     */
    public static Date toDate(Date date) {
        return parseDate(DateFormatUtils.format(date, "yyyy-MM-dd"), "yyyy-MM-dd");
    }

    /**
     * Parses the date string of the form "yyyy-MM-dd" to Date
     * 
     * @param dateString  the date string of the form "yyyy-MM-dd"
     * @return            the date represents the date string day
     * @see               #parseDate(String, String)
     */
    public static Date toDate(String dateString) {
        return parseDate(dateString, "yyyy-MM-dd");
    }

    /**
     * Transforms the date to the time of beginning of the month of the given date
     * <p>
     * For instance: 2015-11-25 17:49:00 will be transformed to 2015-11-01 00:00:00
     * 
     * @param date  the date needs to be transformed
     * @return      date represents the beginning time of the month of given date
     * @see         #parseDate(String, String)
     */
    public static Date toMonth(Date date) {
        return parseDate(DateFormatUtils.format(date, "yyyy-MM"), "yyyy-MM");
    }

    /**
     * Parses the date string of the form "yyyy-MM" to Date
     * 
     * @param dateString  the date string of the form "yyyy-MM"
     * @return            the date represents the beginning time of month of give date
     * @see               #parseDate(String, String)
     */
    public static Date toMonth(String dateString) {
        return parseDate(dateString, "yyyy-MM");
    }

    /**
     * Parses the date string to Date according to specific pattern
     * 
     * @param dateString  the date string needs to be parsed
     * @param pattern     the date string pattern applied when parse undertaken
     * @return            the date of given date string represents
     * @see               #parseDate(String, String[])
     */
    public static Date parseDate(String dateString, String pattern) {
        return parseDate(dateString, new String[] { pattern });
    }

    /**
     * Parses the date string to Date according to specific patterns
     * 
     * @param dateString  the date string needs to be parsed
     * @param patterns    the date string patterns applied respectively when parse undertaken
     * @return            the date of given date string represents
     * @see               org.apache.commons.lang3.time.DateUtils#parseDate(String, String...)
     */
    public static Date parseDate(String dateString, String[] patterns) {
        try {
            return org.apache.commons.lang3.time.DateUtils.parseDate(dateString, patterns);
        } catch (ParseException e) {
            return null;
        }
    }

    /**
     * Given year number, month number, day number of month, returns the date
     * of day they represents
     * 
     * @param year   the year number of date to be parsed
     * @param month  the month number of date to be parsed
     * @param date   the day number of month of date to be parsed
     * @return       date that given year, month, day represents
     */
    public static Date parseDate(int year, int month, int date) {
        return parseDate(DateFormatUtils.format(year, month, date), DEFAULT_DATE_PATTERN);
    }

    /**
     * Adds specific number of days to given date and get a new calculated date
     * 
     * @param date  the date to be added
     * @param days  the number of days to add
     * @return      a new date after addition 
     */
    public static Date addDays(Date date, int days) {
        return org.apache.commons.lang3.time.DateUtils.addDays(date, days);
    }

    /**
     * Adds specific number of weeks to given date and get a new calculated date
     * 
     * @param date   the date to be added
     * @param weeks  the number of weeks to add
     * @return       a new date after addition
     */
    public static Date addWeeks(Date date, int weeks) {
        return org.apache.commons.lang3.time.DateUtils.addWeeks(date, weeks);
    }

    /**
     * Adds specific number of month to given date and get a new calculated date
     * 
     * @param date    the date to be added
     * @param months  the number of months to add
     * @return        a new date after addition
     */
    public static Date addMonths(Date date, int months) {
        return org.apache.commons.lang3.time.DateUtils.addMonths(date, months);
    }

    /**
     * Gets the first day in the month of given date
     * 
     * @param date  the date represents the month
     * @return      the date of the first day of month
     */
    public static Date getFirstMonthDay(Date date) {
        
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        
        return calendar.getTime();
    }

    /**
     * Gets first day of a calendar month page. Assume that the start day of a
     * week is Sunday.
     * <p>
     * There are differences between the first day of the month and the first
     * day of a calendar month page. For instance, first day of calendar
     * month page for 2008.5.18 is 2008.4.27 instead of 2008.5.1.
     * 
     * @param date  the date represents the month
     * @return      the date of the first day of a calendar page
     */
    public static Date getFirstMonthDayForCalendar(Date date) {
        
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        
        // Set first day of week is Sunday
        calendar.setFirstDayOfWeek(0);
        
        calendar.add(Calendar.DAY_OF_YEAR, -(calendar.get(Calendar.DAY_OF_WEEK) - 1));
        return calendar.getTime();
    }

    /**
     * Gets the last day in the month of given date
     * 
     * @param date  the date represents the month
     * @return      the date of the last day of month
     */
    public static Date getLastMonthDay(Date date) {
        
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        
        return calendar.getTime();
    }

    /**
     * Gets last day of a calendar month page. Assume that the start day of a
     * week is Sunday.
     * <p>
     * There are differences between the last day of the month and the last
     * day of a calendar month page. For instance, last day of calendar
     * month page for 2008.6.18 is 2008.7.5 instead of 2008.6.30.
     * 
     * @param date  the date represents the month
     * @return      the date of the last day of a calendar page
     */
    public static Date getLastMonthDayForCalendar(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        
        // Set first day of week is Sunday
        calendar.setFirstDayOfWeek(0);
        
        calendar.add(Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK - calendar.get(Calendar.DAY_OF_WEEK));
        return calendar.getTime();
    }

    /**
     * Checks if the date is Sunday or Saturday
     * 
     * @param date  the date to be checked
     * @return      {@code true} if the date is Sunday or Saturday
     */
    public static boolean isWeekend(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
        return dayOfWeek == 0 || dayOfWeek == 6;
    }

    /**
     * Checks if the date is Friday
     * 
     * @param date
     * @return
     */
    public static boolean isFriday(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
        return dayOfWeek == Calendar.FRIDAY;
    }

    /**
     * Checks if the two dates are the same day
     * 
     * @param date1                      one of two dates to be compared
     * @param date2                      one of two dates to be compared
     * @throws IllegalArgumentException  if any of date is null
     * @return                           {@code true} if two dates are the same day
     */
    public static boolean isSameDay(Date date1, Date date2) {
        if (date1 == null || date2 == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(date1);
        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(date2);
        return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
                cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
                cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
    }
    
    /**
     * Checks if two dates are in the same year and same week
     * 
     * @param date1                      one of two dates to be compared
     * @param date2                      one of two dates to be compared
     * @throws IllegalArgumentException  if any of date is null
     * @return                           {@code true} if tow dates are in the
     *                                   same year and same month
     */
    public static boolean isSameWeek(Date date1, Date date2) {
        if (date1 == null || date2 == null) {
            throw new IllegalArgumentException("The date must not be null");
        }

        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(date1);
        cal1.setFirstDayOfWeek(Calendar.MONDAY);

        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(date2);
        cal2.setFirstDayOfWeek(Calendar.MONDAY);

        return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
                cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
                cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR));        
    }
    
    /**
     * Checks if two dates are in the same year and same month
     * 
     * @param date1                      one of two dates to be compared
     * @param date2                      one of two dates to be compared
     * @throws IllegalArgumentException  if any of date is null
     * @return                           {@code true} if two dates are in the 
     *                                   same year and same month
     */
    public static boolean isSameMonth(Date date1, Date date2) {
        if (date1 == null || date2 == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(date1);
        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(date2);
        return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
                cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
                cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH));
    }

    /**
     * Gets interval days between start date and end date
     * 
     * @param start                      the start date to be computed
     * @param end                        the end date to be computed
     * @throws IllegalArgumentException  if start date is after the end date
     * @return                           a List of Dates between start and end dates
     */
    public static List<Date> getIntervalDays(Date start, Date end) {
        List<Date> dates = new ArrayList<Date>();
        if (start.after(end)) {
            throw new IllegalArgumentException("The start date must not be after end date.");
        } else {
            for(Date date = start; !isSameDay(date, end); date = org.apache.commons.lang3.time.DateUtils.addDays(date, 1)) {
                dates.add(date);
            }
            dates.add(end);
        }
        return dates;
    }

    /**
     * Gets interval weeks between start date and end date
     * 
     * @param start                      the start date to be computed
     * @param end                        the end date to be computed
     * @throws IllegalArgumentException  if start date is after end date
     * @return                           a {@link List} of Date represents every
     *                                   week between start date and end date
     */
    public static List<Date> getIntervalWeeks(Date start, Date end) {
        List<Date> dates = new ArrayList<Date>();
        if (start.after(end)) {
            throw new IllegalArgumentException("The start date must not be after end date.");
        } else {
            for (Date date = start; !isSameWeek(date, end); date = org.apache.commons.lang3.time.DateUtils.addWeeks(date, 1)) {
                dates.add(date);
            }
            dates.add(end);
        }
        return dates;
    }

    /**
     * Gets interval months between start date and end date
     * 
     * @param start                      the start date to be computed
     * @param end                        the end date to be computed
     * @throws IllegalArgumentException  if start date is after end date
     * @return                           a List of Date represents every month
     *                                   between start date and end date
     */
    public static List<Date> getIntervalMonths(Date start, Date end) {
        List<Date> dates = new ArrayList<Date>();
        if (start.after(end)) {
            throw new IllegalArgumentException("The start date must not be after end date.");
        } else {
            for(Date date = start; !isSameMonth(date, end); date = org.apache.commons.lang3.time.DateUtils.addMonths(date, 1)) {
                dates.add(date);
            }
            dates.add(end);
        }
        return dates;
    }

    /**
     * Gets the number of days between start date and end date
     * 
     * @param start the start date
     * @param end   the end date
     * @return      the number of days between start and end date, excluding the
     *              the start date and end date.
     */
    public static int daysBetween(Date start, Date end) {
        List<Date> dates = start.before(end) ? getIntervalDays(start, end) : getIntervalDays(end, start);
        return org.apache.commons.collections.CollectionUtils.size(dates) - 1;
    }

    /**
     * Gets the number of weeks between start day and end day
     * 
     * @param start  the start date
     * @param end    the end date
     * @return       the number of weeks between start and end date, excluding
     *               the start week and end week.
     */
    public static int weeksBetween(Date start, Date end) {
        List<Date> dates = start.before(end) ? getIntervalWeeks(start, end) : getIntervalWeeks(end, start);
        return org.apache.commons.collections.CollectionUtils.size(dates) - 1;
    }

    /**
     * Gets the first day of week. Assume that the first day of week is Monday
     * 
     * @param date  the date represents the week
     * @return      the first day of the week
     */
    public static Date getFirstWeekDay(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.setFirstDayOfWeek(Calendar.MONDAY);
        calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
        return calendar.getTime();
    }

    /**
     * Gets the last day of week. Assume that the last day of week is Sunday
     * 
     * @param date  the date represents the week
     * @return      the last day of the week
     */
    public static Date getLastWeekDay(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.setFirstDayOfWeek(Calendar.MONDAY);
        calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
        return calendar.getTime();
    }

    /**
     * Gets the specific day of week. Assume that the first day of week is Monday
     * 
     * @param date   the date represents the week
     * @param field  the value of {@link Calendar#DAY_OF_WEEK} field
     * @return       the specific day of week
     */
    public static Date getDayOfWeek(Date date, int field) {
        return getDayOfWeek(date, Calendar.MONDAY, field);
    }

    /**
     * Gets the specific day of week.
     * 
     * @param date                 the date represents the week
     * @param firstDayOfWeekField  the value of {@link Calendar#DAY_OF_WEEK} field stands for first day of week
     * @param field                the value of {@link Calendar#DAY_OF_WEEK} field
     * @return                     the specific day of week
     */
    public static Date getDayOfWeek(Date date, int firstDayOfWeekField, int field) {
        // validate field and firstDayOfWeekField
        if ((field > Calendar.SATURDAY || field < Calendar.SUNDAY) ||
                (firstDayOfWeekField > Calendar.SATURDAY || field < Calendar.SUNDAY)) {
            return date;
        }
        
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.setFirstDayOfWeek(firstDayOfWeekField);
        calendar.set(Calendar.DAY_OF_WEEK, field);
        return calendar.getTime();
    }
    
    /**
     * 
     * @return
     */
    public static List<String> getMonthRemainDays(){
    	//String date = null;
    	
    	//Calendar a = Calendar.getInstance();
    	//int day = a.get(Calendar.DAY_OF_MONTH);
		//a.set(Calendar.DATE, 1);//设置本月的第N天为开始日期
		//a.roll(Calendar.DATE, -1);//日期回滚一天，也就是最后一天
		//int maxDate = a.get(Calendar.DATE); //本月剩余的天数
		
		//String  thisMonth = DateFormatUtils.format(new Date(), "yyyy-MM-dd");
		//获取当下时间和往后的30天
		Date toNextDay = new Date();
		List<String> dates = new ArrayList<>();
		while(dates.size() <= 30) {
			
			dates.add(DateFormatUtils.format(toNextDay,"yyyy-MM-dd"));
			
			toNextDay = DateUtils.addDays(toNextDay,1 );			
			
		}
		
    	
    	
    	return dates;
    }

}
